/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { ArkClass, ArkFile, ViewTreeNode } from 'arkanalyzer';
import { BaseMatcher, MatcherCallback, MatcherTypes } from '../Matchers';
import Logger, { LOG_MODULE_TYPE } from 'arkanalyzer/lib/utils/logger';

const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'matchCommon');

export function matchCommon(arkFiles: ArkFile[], matchers: MatcherCallback[]): void {
    const needWalkStmt = !!matchers.find(f => f.matcher?.matcherType === MatcherTypes.STMT ||
        (f.matcher?.matcherType === MatcherTypes.MIXED && (f.matcher as CompositeMatcher).hasMatcherType(MatcherTypes.STMT)));
    for (const arkFile of arkFiles) {
        check(arkFile, MatcherTypes.FILE, matchers);
        arkFile.getNamespaces().forEach(ns => {
            check(ns, MatcherTypes.NAMESPACE, matchers);
            ns.getClasses().forEach(cls => matchClass(cls, matchers, needWalkStmt));
        });
        arkFile.getClasses().forEach(cls => matchClass(cls, matchers, needWalkStmt));
    }
}

function matchClass(cls: ArkClass, matchers: MatcherCallback[], needWalkStmt: boolean): void {
    check(cls, MatcherTypes.CLASS, matchers);
    if (cls.hasViewTree()) {
        const root = cls.getViewTree()?.getRoot();
        if (root) {
            matchViewTreeNode(root, matchers);
        }
    }
    cls.getMethods().forEach(mtd => {
        check(mtd, MatcherTypes.METHOD, matchers);
        if (needWalkStmt) {
            mtd.getCfg()?.getStmts().forEach(s => check(s, MatcherTypes.STMT, matchers));
        }
    });
    cls.getFields().forEach(field => {
        check(field, MatcherTypes.FIELD, matchers);
        if (needWalkStmt) {
            field?.getInitializer().forEach(s => check(s, MatcherTypes.STMT, matchers));
        }
    });
}

function matchViewTreeNode(node: ViewTreeNode, matchers: MatcherCallback[], visited: Set<ViewTreeNode> = new Set()): void {
    if (visited.has(node)) {
        return;
    } else {
        visited.add(node);
    }
    check(node, MatcherTypes.VIEW_TREE_NODE, matchers);
    node.children.forEach((child) => {
        matchViewTreeNode(child, matchers, visited);
    });
}

function check(node: any, type: MatcherTypes, matchers: MatcherCallback[]): void {
    for (const item of matchers) {
        checkOneCallback(node, type, item);
    }
}

function checkOneCallback(node: any, type: MatcherTypes, callback: MatcherCallback): void {
    try {
        let matcher1 = callback.matcher;
        if (matcher1 && matcher1.matcherType === type && hasMatch(matcher1) && matcher1.match(node)) {
            callback.callback(node);
        } else if (matcher1 instanceof CompositeMatcher) {
            matcher1.setCurMatcherType(type);
            if (matcher1.match(node)) {
                callback.callback(node);
            }
        }
    } catch (error) {
        logger.error(`Error in common callback ${error}`);
    }
}

function hasMatch(obj: any): obj is { match: (node: any) => boolean } {
    return 'match' in obj && typeof obj.match === 'function';
}

export class CompositeMatcher implements BaseMatcher<any> {
    readonly matcherType = MatcherTypes.MIXED;
    private matchers: Map<MatcherTypes, BaseMatcher<any>> = new Map();
    private curStep: MatcherTypes;

    private targetStep: number = -1;
    private failedStep: number = MatcherTypes.MIXED;

    constructor(matchers: BaseMatcher<any>[]) {
        for (const matcher of matchers) {
            this.matchers.set(matcher.matcherType, matcher);
            if (matcher.matcherType > this.targetStep) {
                this.targetStep = matcher.matcherType;
            }
        }
    }

    public hasMatcherType(type: MatcherTypes): boolean {
        return this.matchers.has(type);
    }

    public setCurMatcherType(type: MatcherTypes): void {
        this.curStep = type;
    }

    public match(node: any): boolean {
        //上游匹配失败
        if (this.curStep > this.failedStep) {
            return false;
        }
        //重新从上游匹配
        const matcher = this.matchers.get(this.curStep);
        if (matcher && hasMatch(matcher) && !matcher.match(node)) {
            this.failedStep = this.curStep;
        } else {
            this.failedStep = MatcherTypes.MIXED;
            //目标匹配成功
            if (this.targetStep === this.curStep) {
                return true;
            }
        }
        return false;
    }

}